package org.ouwh.fw.db;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class JdbcPool {
	private static Log log = LogFactory.getLog(JdbcPool.class);

	private ArrayList<ConnectionWrapper> freeConns = new ArrayList<ConnectionWrapper>();

	private HashMap<ConnectionWrapper, Object> usingConns = new HashMap<ConnectionWrapper, Object>();

	private boolean isOpen = false;

	private CheckThread checkThread;

	private boolean isThreadDead;

	private volatile String jdbcURL = null;

	private volatile String jdbcUserName = null;

	private volatile String jdbcPassword = null;

	private volatile String jdbcDriver = null;

	private volatile int maxSize = 10;

	private volatile int minSize = 1;

	private volatile long interval = 60000;

	private volatile long timeout = 60000;

	public String getJdbcURL() {
		return jdbcURL;
	}

	public void setJdbcURL(String jdbcURL) {
		this.jdbcURL = jdbcURL;
	}

	public String getJdbcUserName() {
		return jdbcUserName;
	}

	public void setJdbcUserName(String jdbcUserName) {
		this.jdbcUserName = jdbcUserName;
	}

	public String getJdbcDriver() {
		return jdbcDriver;
	}

	public void setJdbcDriver(String jdbcDriver) {
		this.jdbcDriver = jdbcDriver;
	}

	public int getMaxSize() {
		return maxSize;
	}

	public void setMaxSize(int maxSize) {
		this.maxSize = maxSize;
	}

	public int getMinSize() {
		return minSize;
	}

	public void setMinSize(int minSize) {
		this.minSize = minSize;
	}

	public long getInterval() {
		return interval;
	}

	public void setInterval(long interval) {
		this.interval = interval;
	}

	public void setJdbcPassword(String jdbcPassword) {
		this.jdbcPassword = jdbcPassword;
	}

	public void setTimeout(long timeout) {
		this.timeout = timeout;
	}

	public long getTimeout() {
		return timeout;
	}

	private boolean compareConnectionMetadata(ConnectionWrapper wrapper, String jdbcDriver, String jdbcURL) {
		return wrapper.getJdbcDriver().equals(jdbcDriver) && wrapper.getJdbcURL().equals(jdbcURL);
	}

	public JdbcPool() {
	}

	public JdbcPool(String jdbcURL, String jdbcUserName, String jdbcPassword, String jdbcDriver, int maxSize,
			int minSize, long interval, long timeout) {
		this.jdbcURL = jdbcURL;
		this.jdbcUserName = jdbcUserName;
		this.jdbcPassword = jdbcPassword;
		this.jdbcDriver = jdbcDriver;
		this.maxSize = maxSize;
		this.minSize = minSize;
		this.interval = interval;
		this.timeout = timeout;
	}

	public synchronized void open() {
		if (!isOpen) {
			isOpen = true;
			checkThread = new CheckThread();
			checkThread.setDaemon(false);
			checkThread.setName("jdbc-pool check-thread");
			checkThread.start();
		}
	}

	public synchronized Connection getConnection() {
		ConnectionWrapper wrapper = null;
		if (isOpen) {
			try {
				wrapper = freeConns.remove(0);
				wrapper.getRawConnection().setAutoCommit(false);
			} catch (Exception e) {
				try {
					wrapper.getRawConnection().close();
				} catch (Exception ee) {
				}
				wrapper = null;
			}
			String tmpJdbcDriver = getJdbcDriver();
			String tmpJdbcUrl = getJdbcURL();
			if ((wrapper != null) && (!compareConnectionMetadata(wrapper, tmpJdbcDriver, tmpJdbcUrl))) {
				try {
					wrapper.getRawConnection().close();
				} catch (Exception ee) {
				}
				wrapper = null;
			}
			if ((wrapper == null) && (size() < maxSize)) {
				try {
					Class.forName(tmpJdbcDriver);
					wrapper = new ConnectionWrapper(DriverManager.getConnection(tmpJdbcUrl, jdbcUserName, jdbcPassword),
							this);
					wrapper.getRawConnection().setAutoCommit(false);
					wrapper.setJdbcDriver(tmpJdbcDriver);
					wrapper.setJdbcURL(tmpJdbcUrl);
				} catch (Exception e) {
					log.error("", e);
					try {
						wrapper.getRawConnection().close();
					} catch (Exception ee) {
					}
					wrapper = null;
				}
			}
			if (wrapper != null) {
				wrapper.markOwnerThread();
				usingConns.put(wrapper, null);
			}
		}
		return (null == wrapper) ? null : wrapper.newProxy();
	}

	public synchronized void releaseConnection(ConnectionWrapper wrapper) {
		if (isOpen && (wrapper != null)) {
			usingConns.remove(wrapper);
			try {
				wrapper.getRawConnection().rollback();
				wrapper.getRawConnection().setAutoCommit(false);
				String tmpJdbcDriver = getJdbcDriver();
				String tmpJdbcUrl = getJdbcURL();
				if (compareConnectionMetadata(wrapper, tmpJdbcDriver, tmpJdbcUrl) && (size() < getMaxSize())) {
					freeConns.add(wrapper);
				} else {
					try {
						wrapper.getRawConnection().close();
					} catch (Exception ee) {
					}
				}
			} catch (Exception e) {
				try {
					wrapper.getRawConnection().close();
				} catch (Exception ee) {
				}
			}
		}
	}

	public synchronized void close() {
		if (isOpen) {
			isOpen = false;
			checkThread.isRunning = false;
			checkThread.interrupt();

			for (int i = 0; i < freeConns.size(); i++) {
				ConnectionWrapper wrapper = freeConns.get(i);
				try {
					wrapper.getRawConnection().close();
				} catch (Exception e) {
				}
			}
			Iterator<ConnectionWrapper> iterator = usingConns.keySet().iterator();
			while (iterator.hasNext()) {
				ConnectionWrapper wrapper = iterator.next();
				try {
					wrapper.getRawConnection().rollback();
				} catch (Exception e) {
				}
				try {
					wrapper.getRawConnection().close();
				} catch (Exception e) {
				}
			}
			freeConns.clear();
			usingConns.clear();
		}
		while (!isThreadDead) {
			try {
				Thread.sleep(100);
			} catch (Exception e) {
			}
		}
	}

	public synchronized int size() {
		if (isOpen) {
			return usingConns.size() + freeConns.size();
		} else {
			return 0;
		}
	}

	public synchronized int freeSize() {
		if (isOpen) {
			return freeConns.size();
		} else {
			return 0;
		}
	}

	private synchronized void closeFreeConnection() {
		if (isOpen) {
			log.info("JdbcPool start cleaning...");
			int num = size() - minSize;
			for (int i = 0; i < num; i++) {
				ConnectionWrapper wrapper = null;
				for (int j = freeConns.size() - 1; j >= 0; j--) {
					if (System.currentTimeMillis() - freeConns.get(j).getReturnTime() > timeout) {
						wrapper = freeConns.remove(j);
						break;
					}
				}
				if (wrapper != null) {
					try {
						wrapper.getRawConnection().close();
					} catch (Exception e) {
					}
				}
			}
			log.info("JdbcPool cleaning finish, " + "size: " + size() + ", free size: " + freeSize());
		}
	}

	private class CheckThread extends Thread {
		private boolean isRunning = true;

		public void run() {
			isThreadDead = false;
			while (isRunning) {
				try {
					Thread.sleep(interval);
					closeFreeConnection();
				} catch (Exception e) {
				}
			}
			isThreadDead = true;
		}
	}

	public void dumpBusyThreads() {
		if (log.isDebugEnabled()) {
			Set<ConnectionWrapper> usingConnectionSet = usingConns.keySet();
			for (ConnectionWrapper wrapper : usingConnectionSet) {
				Thread thread = wrapper.getOwnerThread();
				if (thread != null) {
					StackTraceElement[] stacks = wrapper.getOwnerThreadStack();
					StringBuffer sb = new StringBuffer();
					sb.append("JdbcPool busy thread dump: " + thread.getName() + "\r\n");
					if (stacks != null) {
						for (StackTraceElement stack : stacks) {
							sb.append("  at ");
							sb.append(stack.toString());
							sb.append("\r\n");
						}
					}
					log.debug(sb.toString());
				}
			}
		}
	}
}
